/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.pz.net;

import com.pz.util.MessageMannager;
import com.pz.util.Log;
import java.io.IOException;
import java.nio.channels.DatagramChannel;
import java.net.SocketAddress;
import java.nio.ByteBuffer;

import static com.pz.util.Log.d;
import static com.pz.util.ByteBufferHelper.getCommand;
import static com.pz.util.ByteBufferHelper.put;
import static com.pz.net.Command.*;

/**
 *
 * @author jannek
 */
public class Other {

    private final int size = 40;
    private NetworkListener listener;
    private byte connect;
    private byte disconnectReason;
    private long disconnectTime;

    public final int id;
    public final int myId;
    public final int key;
    public final String name;

    private final SocketAddress address;
    private final ByteBuffer confirm;

    private final ByteBuffer outbox;
    private final int[][] outboxIndex; // add field for delay // later
    private int outboxCount;
    private short lastSent;

    private final ByteBuffer quickInbox;
    private final ByteBuffer inbox;
    private final short[][] inboxIndex;
    private int inboxCount;
    private short lastReceived;
    private final short[] lastInput;
    private int lastInputIndex;

    private short receivedId;
    private short receivedRequired;
    private short receivedLenght;

    public Other(int id, int myId, int key, String name,
            SocketAddress address) {
        this.id = id;
        this.myId = myId;
        this.key = key;
        this.name = name;
        this.address = address;

        outbox = ByteBuffer.allocate(MessageMannager.MAX_LENGHT_TOTAL * size);
        outbox.clear();
        outboxIndex = new int[size][2];
        outboxCount = 0;
        lastSent = 0;

        quickInbox = ByteBuffer.allocate(MessageMannager.MAX_LENGHT);
        inbox = ByteBuffer.allocate(MessageMannager.MAX_LENGHT * size);
        inbox.clear();
        inboxIndex = new short[size][3];
        inboxCount = 0;
        lastReceived = 0;
        lastInput = new short[10];
        lastInputIndex = 0;

        confirm = ByteBuffer.allocate(11);
        put(UDP_CONFIRM, confirm);
        confirm.putInt(myId);
        confirm.putInt(key);

        connect = 0;
    }

    public void connect(NetworkListener listener) {
        this.listener = listener;
        connect();
    }

    private void connect() {
        switch (connect) {
            case 0:
                outboxIndex[0][0] = lastSent;
                outboxIndex[0][1] = 7;
                outbox.clear();
                outbox.putInt(myId);
                outbox.put(MessageMannager.HELLO);
                outbox.putShort(lastSent);
                outboxCount = 1;
                connect = 1;
                break;
            case 4:
                outbox.clear();
                outbox.putInt(myId);
                outbox.put(MessageMannager.ISEEYOU);
                outbox.putShort(++lastSent);
                outboxCount = 1;
                outboxIndex[0][0] = lastSent;
                outboxIndex[0][1] = 7;
                connect = 5;
                break;
            case 8:
                Log.d("Connected to " + name + " outbox count " + outboxCount);
                listener.onConnect(this);
                break;
            case 12:
                Log.d("Disconnected " + name);
                listener.onDisconnect(this, disconnectReason);
                break;
        }
        Log.d("Connect " + connect);
    }

    public boolean isConnected() {
        return connect == 8;
    }

    public boolean isDisconnected() {
        return connect == 12;
    }

    public byte disconnectReason() {
        return disconnectReason;
    }

    public int remaining() {
        return outboxCount + inboxCount;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    protected void flush(DatagramChannel channel) throws IOException {
        for (int i = 0, n = 0; i < size && n < outboxCount; i++) {
            if (outboxIndex[i][1] != 0) {
                n++;
                if (i != 0) {
                    Log.d("pos = " + (i * MessageMannager.MAX_LENGHT_TOTAL));
                }
                outbox.clear();
                outbox.position(i * MessageMannager.MAX_LENGHT_TOTAL);
                outbox.limit(outbox.position() + outboxIndex[i][1]);
                channel.send(outbox, address);
            }
        }
        if (connect == 11 && outboxCount == 0) {
            connect = 12;
            connect();
        }
    }

    protected void sendConfirm(short messageId, DatagramChannel udp)
            throws IOException {
        confirm.putShort(9, messageId);
        confirm.clear();
        udp.send(confirm, address);
    }

    protected void confirm(short messageId) {
        // TODO
    }

    public boolean send(ByteBuffer data) {
        if (isConnected()) {
            for (int i = 0; i < size; i++) {
                if (outboxIndex[i][1] == 0) {
                    outbox.clear();
                    outbox.position(i * MessageMannager.MAX_LENGHT_TOTAL);
                    outbox.putInt(myId);
                    outbox.put(MessageMannager.SEND);
                    outbox.putShort(lastSent);
                    outbox.putShort(++lastSent);
                    outbox.putShort((short) data.remaining());
                    outboxIndex[i][0] = lastSent;
                    outboxIndex[i][1] = data.remaining() + 11;
                    outbox.put(data);
                    data.flip();
                    outboxCount++;
                    return true;
                }
            }
        }
        // !!!!!!!!!!!!!!!! outbox is full
        return false;
    }

    protected boolean disconnect(byte reason) {
        disconnectReason = reason;
        return disconnect();
    }

    private boolean disconnect() {
        connect += 2;
        Log.d(name + ".disconnect() - connect: " + connect);
        disconnectTime = System.nanoTime();
        for (int i = 0; i < size; i++) {
            if (outboxIndex[i][1] == 0) {
                outbox.clear();
                outbox.position(i * MessageMannager.MAX_LENGHT_TOTAL);
                outbox.putInt(myId);
                outbox.put(MessageMannager.BYE);
                outbox.put(disconnectReason);
                outbox.putShort(lastSent);
                outbox.putShort(++lastSent);
                outboxIndex[i][0] = lastSent;
                outboxIndex[i][1] = 10;
                outboxCount++;
                return true;
            }
        }
        return false;
    }

    private boolean receive(ByteBuffer data) {
        if (inboxCount == 0) {
            return false;
        }

        for (int i = 0; i < size; i++) {
            if (inboxIndex[i][0] != 0 && inboxIndex[i][2] == lastReceived) {
                if (inboxIndex[i][0] == -1) {
                    connect++;
                    Log.d(name + ".receive(BYE) - connect: " + connect);
                    if (connect == 9) {
                        disconnect();
                    }
                    return false;
                }
                lastReceived = inboxIndex[i][1];
                inbox.position(i * MessageMannager.MAX_LENGHT);
                inbox.limit(inbox.position() + inboxIndex[i][0]);
                data.clear();
                data.put(inbox);
                data.flip();
                inboxIndex[i][0] = 0;
                return true;
            }
        }
        return false;
    }

    protected void input(short messageId, short required, ByteBuffer data) {



        switch (data.get()) {
            case MessageMannager.CONFIRM:
                short message = data.getShort();
//				Log.d("CONFIRM!! " + message + " received from " + name);
                for (int i = 0; i < size; i++) {
                    if (outboxIndex[i][0] == message
                            && outboxIndex[i][1] != 0) {
                        outboxIndex[i][1] = 0;
                        outboxCount--;
                        if (connect < 8 && (connect == 1 || connect == 3
                                || connect == 5 || connect == 7)) {
                            Log.d("CONFIRM!! received from " + name);
                            connect++;
                            connect();
                        }
                        return null;
                    }
                }
                break;
            case MessageMannager.SEND:
                receivedRequired = data.getShort();
                receivedId = data.getShort();
                receivedLenght = data.getShort();

                Log.d("SEND!! #" + receivedId + " of " + receivedLenght
                        + " bytes. From " + name);

                for (short s : lastInput) {
                    if (s == receivedId) {
                        confirm.clear();
                        confirm.putShort(5, receivedId);
                        return confirm;
                    }
                }

                lastInput[lastInputIndex++] = receivedId;
                if (lastInputIndex >= 10) {
                    lastInputIndex = 0;
                }

                ByteBuffer bb = null;
                if (receivedRequired == lastReceived) {
                    lastReceived = receivedId;
                    Log.d("QUICK!!");
                    quickInbox.clear();
                    bb = quickInbox;
                } else {
                    Log.d("NOT QUICK!!");
                    for (int i = 0; i < size; i++) {
                        if (inboxIndex[i][0] == 0) {
                            inboxIndex[i][2] = receivedRequired;
                            inboxIndex[i][1] = receivedId;
                            inboxIndex[i][0] = receivedLenght;

                            inbox.clear();
                            inbox.position(i * MessageMannager.MAX_LENGHT);
                            bb = inbox;
                            inboxCount++;
                            break;
                        }
                    }
                }

                if (bb == null) {
                    break;
                    // !!!!!!!!!!!!!!!!!!!!!! inbox is full
                }

                bb.put(data);

                if (quickInbox == bb) {
                    bb.flip();
                    listener.onMessage(bb, this);

                    while (receive(bb)) {
                        listener.onMessage(bb, this);
                    }
                }

                confirm.clear();
                confirm.putShort(5, receivedId);
                return confirm;
            case MessageMannager.BYE:

                byte reason = data.get();
                receivedRequired = data.getShort();
                receivedId = data.getShort();

                for (short s : lastInput) {
                    if (s == receivedId) {
                        confirm.clear();
                        confirm.putShort(5, receivedId);
                        return confirm;
                    }
                }

                lastInput[lastInputIndex++] = receivedId;
                if (lastInputIndex >= 10) {
                    lastInputIndex = 0;
                }

                disconnectReason = reason;
                Log.d(name + ".input(BYE) before - connect: " + connect);

                if (receivedRequired == lastReceived) {
                    connect++;
                    Log.d(name + ".input(BYE) - connect: " + connect);
                    if (connect == 9) {
                        disconnect();
                    }
                } else {
                    for (int i = 0; i < size; i++) {
                        if (inboxIndex[i][0] == 0) {
                            inboxIndex[i][2] = receivedRequired;
                            inboxIndex[i][1] = receivedId;
                            inboxIndex[i][0] = -1;
                            inboxCount++;
                        }
                    }
                }

                confirm.clear();
                confirm.putShort(5, receivedId);
                return confirm;

            case MessageMannager.HELLO:
                lastReceived = data.getShort();
                confirm.clear();
                confirm.putShort(5, lastReceived);

                if (connect == 1 || connect == 2) {
                    Log.d("HELLO!! received from " + name);
                    connect += 2;
                    connect();
                }

                return confirm;
            case MessageMannager.ISEEYOU:
                lastReceived = data.getShort();
                confirm.clear();
                confirm.putShort(5, lastReceived);

                if (connect == 5 || connect == 6) {
                    Log.d("ISEEYOU!! received from " + name);
                    connect += 2;
                    connect();
                }

                return confirm;
        }

        return null;
    }

    @Override
    public String toString() {
        return name + " @" + address;
    }
}
